﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net.Security;
using System.IO;
using System.Security.Authentication;
using System.Security;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography;


namespace App2Wpf
{
    public class ClientIO
    {
        private const int PORT = 8021;

        private string RemoteAddress;

        private int RemotePort;

        private bool UseTLSv1_3;

        public TcpClient Client;

        public SslStream SSLStream;

        public StreamWriter Writer;

        public StreamReader Reader;

        public BinaryReader BinaryReader;




        public ClientIO(
            string RemoteAddress = "127.0.0.1",
            int RemotePort = PORT,
            bool UseTLSv1_3 = false)

        {
            this.RemoteAddress = RemoteAddress;
            this.RemotePort = RemotePort;
            this.UseTLSv1_3 = UseTLSv1_3;
            
        }



        public static bool validateservercertificate(
               object sender,
               X509Certificate certificate,
               X509Chain chain,
               SslPolicyErrors sslpolicyerrors)
        {
            if (sslpolicyerrors == SslPolicyErrors.None)
                return true;

            Console.WriteLine("certificate error: {0}", sslpolicyerrors);

            // do not allow this client to communicate with unauthenticated servers.

            //return false;
            return true;

        }


        public void Connect()
        {
            this.Client =  new TcpClient(this.RemoteAddress, this.RemotePort);
            if (UseTLSv1_3)
            {
                SslProtocols TLSVersion = SslProtocols.Tls11;
                clientFunc.Showlog($"Establish an encrypted tunnel using: ${TLSVersion}...");
            }
            else
            {
                SslProtocols TLSVersion = SslProtocols.Tls12;
                clientFunc.Showlog($"Establish an encrypted tunnel using: ${TLSVersion}...");
            }

            RemoteCertificateValidationCallback userCertificateValidationCallback = new RemoteCertificateValidationCallback(validateservercertificate);

            this.SSLStream = new SslStream(
                this.Client.GetStream(),
                false,

                userCertificateValidationCallback
                );

            this.SSLStream.AuthenticateAsClient(
                "PowerRemoteDesktop",
                null,
                SslProtocols.Tls12,
                false);

            if (!this.SSLStream .IsEncrypted ) 
            {


                //throw "Could not establish a secure communication channel with remote server."; 
                //e.Message ="Could not establish a secure communication channel with remote server.";

            }


            this.SSLStream.WriteTimeout = 5000;
            this.Writer = new StreamWriter(this.SSLStream );
            this.Writer.AutoFlush = true;

            this.Reader = new StreamReader(this.SSLStream);
            this.BinaryReader = new BinaryReader(this.SSLStream);
            clientFunc.Showlog("Encrypted tunnel opened and ready for use.");

        }


        public void Authentify(SecureString SecurePassword)
        {
            string candidate = this.Reader.ReadLine();
            var challengeSolution = ResolveAuthenticationChallenge(candidate, SecurePassword);

            
            this.Writer.WriteLine(challengeSolution);


            var result = this.Reader.ReadLine();
            if (result == ProtocolCommand.Success.ToString())
            {
                clientFunc.Showlog("Solution accepted. Authentication success.");
            }
            else
            {
                clientFunc.Showlog("Solution declined. Authentication failed.");
            }
        }

        public string rRemoteAddress() {
            return this.Client.Client.RemoteEndPoint.AddressFamily.ToString();
        }

        public int rRemotePort()
        {
            //return this.Client.Client.RemoteEndPoint.Port;
            return PORT;

        }

        public string LocalAddress()
        {
            return this.Client.Client.LocalEndPoint.AddressFamily.ToString();
        }

        public int LocalPort()
        {
            //return this.Client.Client.LocalEndPoint.Port;
            return PORT;

        }

        public string ReadLine(int Timeout)
        {
            var defaultTimeout = this.SSLStream.ReadTimeout;
            try
            {
                this.SSLStream.ReadTimeout = Timeout;
                return this.Reader.ReadLine();
            }
            catch (Exception)
            {

                throw;
            }
            finally
            {
                this.SSLStream.ReadTimeout = defaultTimeout;

            }
        }

        public string ReadLine()
        {
            return this.Reader.ReadLine();
        }


        public void WriteJson(PSCustomObject obj)
        {
            var psobj = clientFunc.serializeToJson(obj);

            this.Writer.WriteLine(psobj);
        }

        public void WriteJson(object obj)
        {
            try
            {
                var psobj = clientFunc.serializeToJson(obj);

                this.Writer.WriteLine(psobj);
            }
            catch (Exception)
            {

                throw;
            }
            
        }

        public void WriteLine(int value)
        {
            this.Writer.WriteLine(value);
        }

        public void WriteString(string val)
        {
            this.Writer.WriteLine(val);
        }

        //public PSCustomObject ReadJson(int Timeout)
        //{
        //    var sTimeout = this.ReadLine(Timeout);

        //    PSCustomObject obj = new PSCustomObject();

        //    var objforJson = clientFunc.JsonToObject(Timeout.ToString(), obj);

        //    return (PSCustomObject)objforJson;
        //}

        

        public ServerInformation ReadServerInfoJson()
        {

            var res = this.ReadLine();

            ServerInformation serverinfo = new ServerInformation();

            var psobj = clientFunc.JsonToServerObject(res, serverinfo);

            return (ServerInformation)psobj;
        }


        public void Close()
        {
            if (this.Writer != null)
            {
                this.Writer.Close();
            }

            if (this.Reader != null)
            {
                this.Reader.Close();
            }

            if (this.BinaryReader != null)
            {
                this.BinaryReader.Close();
            }

            if (this.SSLStream != null)
            {
                this.SSLStream.Close();
            }

            if (this.Client != null)
            {
                this.Client.Close();
            }
        }

        public String ResolveAuthenticationChallenge(string candidate, SecureString securePassword)
        {

            IntPtr BSTR = System.Runtime.InteropServices.Marshal.SecureStringToBSTR(securePassword);
            try
            {
                string solution = string.Format("{0}:{1}", candidate, System.Runtime.InteropServices.Marshal.PtrToStringBSTR(BSTR));

                for (int i = 0; i <= 1000; i++)
                {
                    solution = clientFunc.SHA512Encrypt(solution);
                }

                return solution;
            }
            finally
            {
                System.Runtime.InteropServices.Marshal.FreeBSTR(BSTR);
            }
        }



    }
}
